package com.yzcrm.utils;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.setting.Setting;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.*;
import com.jfinal.kit.Prop;
import com.jfinal.kit.PropKit;
import com.jfinal.log.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class FileUploadUtil {

    private final static Log LOG = Log.getLog(FileUploadUtil.class);

    private static Setting FILE_SETTING = new Setting(FileUtil.touch("file_upload.setting"),CharsetUtil.CHARSET_UTF_8, true);

    private static final long PART_SIZE = 5 * 1024 * 1024L; // 每个Part的大小，最小为5MB
    private static final int CONCURRENCIES = 5; // 上传Part的并发线程数。

    private static Prop p = PropKit.use("config/aliyun.txt");
    public static OSS ossClient;

    private String uuid = null;
    private String url = null;

    public String getUuid() {
        return uuid;
    }

    public void setUuid(String uuid) {
        this.uuid = uuid;
    }

    static {
//        return new OSSClient(p.get("ENDPOINT"), p.get("ACCESS_KEY_ID"), p.get("ACCESS_KEY_SECRET"));
        ossClient = new OSSClientBuilder().build(p.get("ENDPOINT"), p.get("ACCESS_KEY_ID"), p.get("ACCESS_KEY_SECRET"));
    }

    /**
     * 文件预览地址获取
     * @param objectName
     * @return
     */
    public static String getPreviewURL(String objectName) {

//        try {
//            URL url = new URL(objectName);
//            objectName = url.getPath();
//            if (objectName.length() > 0) {
//                objectName = objectName.substring(1);
//            }
//            Log.getLog(FileUploadUtil.class).info("文件路径：" + objectName);
//
//
//        } catch (MalformedURLException e) {
//            e.printStackTrace();
//        }
//
//        // 创建OSSClient实例。
//        OSS ossClient = new OSSClientBuilder().build(p.get("ENDPOINT"), p.get("ACCESS_KEY_ID"), p.get("ACCESS_KEY_SECRET"));
//        // 设置权限(公开读)
//        ossClient.setBucketAcl(p.get("BACKET_NAME"), CannedAccessControlList.PublicRead);
//        Date expiration = new Date(new Date().getTime() + 60 * 1000 * 10);
////        GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest(p.get("BACKET_NAME"), "SAAS-CRM/f04a213fe6cd4a9795aee81d7370d182customer.xls", HttpMethod.GET);
//        GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest(p.get("BACKET_NAME"), objectName, HttpMethod.GET);
//        req.setExpiration(expiration);
//        req.setProcess("imm/previewdoc,copy_0");
//
//        URL signedUrl = ossClient.generatePresignedUrl(req);
//        // 关闭OSSClient。
//        ossClient.shutdown();
//        if (signedUrl != null) {
//            Log.getLog(FileUploadUtil.class).info("OSS文件签名地址：" + signedUrl.toString());
//            return signedUrl.toString();
//        }
        return null;

    }

    /**
     * 上传文件到OSS
     * @param ossClient
     * @param file
     * @param bucketName
     * @param folder
     * @return
     */
    public void uploadOss(OSS ossClient, File file, String bucketName, String folder) {

        // 文件名
        String fileName = file.getName();
        fileName = UUID.randomUUID().toString().replaceAll("-","") + fileName;

        // 生成上传后目标地址
        url = "https://" + p.get("BACKET_NAME") + "." + p.get("ENDPOINT") + "/" + p.get("FOLDER") + "/" + fileName;
        Log.getLog(FileUploadUtil.class).info("上传的文件路径：" + url);

        try {

            if(file.length() > PART_SIZE) {

                // 使用multipart的方式上传文件
                LOG.info("正在上传...");
                long startTime = System.currentTimeMillis();
                uploadBigFile(ossClient, bucketName, folder + "/" + fileName, file);
                long endTime = System.currentTimeMillis();
                LOG.info("上传花费时间约：" + (endTime - startTime)/1000 + " s");

            } else {

                // 以输入流的形式上传文件
                uploadFile(ossClient,file,bucketName,folder,fileName);

                // 保存上传进度
                this.saveUploadProcess(1, 1,1, url);
            }


        } catch (Exception e) {

            e.printStackTrace();
            Log.getLog(FileUploadUtil.class).info("上传阿里云异常:" + e.getMessage());

            // 保存上传进度
            this.saveUploadProcess(0, 0,0, null);
        }

    }

    /**
     * 删除文件播放记录
     * @param uuid
     */
    public static void removeFileUploadProcess(String uuid) {

        FileUploadUtil.FILE_SETTING.remove(uuid);
    }

    /**
     * 获取文件记录
     * @param uuid
     * @return
     */
    public static String getFileUploadProcess(String uuid) {

        return FileUploadUtil.FILE_SETTING.get(uuid);
    }


    public static String getContentType(String fileName) {
        // 文件的后缀名
        String fileExtension = fileName.substring(fileName.lastIndexOf("."));
        if (".bmp".equalsIgnoreCase(fileExtension)) {
            return "image/bmp";
        }
        if (".gif".equalsIgnoreCase(fileExtension)) {
            return "image/gif";
        }
        if (".jpeg".equalsIgnoreCase(fileExtension) || ".jpg".equalsIgnoreCase(fileExtension)
                || ".png".equalsIgnoreCase(fileExtension)) {
            return "image/jpeg";
        }
        if (".html".equalsIgnoreCase(fileExtension)) {
            return "text/html";
        }
        if (".txt".equalsIgnoreCase(fileExtension)) {
            return "text/plain";
        }
        if (".vsd".equalsIgnoreCase(fileExtension)) {
            return "application/vnd.visio";
        }
        if (".ppt".equalsIgnoreCase(fileExtension) || "pptx".equalsIgnoreCase(fileExtension)) {
            return "application/vnd.ms-powerpoint";
        }
        if (".doc".equalsIgnoreCase(fileExtension) || "docx".equalsIgnoreCase(fileExtension)) {
            return "application/msword";
        }
        if (".xml".equalsIgnoreCase(fileExtension)) {
            return "text/xml";
        }
        if (".flv".equalsIgnoreCase(fileExtension) || ".f4v".equalsIgnoreCase(fileExtension)) {
            return "application/octet-stream";
        }
        if (".mp4".equalsIgnoreCase(fileExtension)) {
            return "video/mp4";
        }
        if (".ogv".equalsIgnoreCase(fileExtension)) {
            return "video/ogg";
        }
        if (".webm".equalsIgnoreCase(fileExtension)) {
            return "video/webm";
        }
        // 默认返回空
        return "";
    }

    /**
     * 以输入流的形式上传文件
     * @param ossClient
     * @param file
     * @param bucketName
     * @param folder
     * @param fileName
     * @throws IOException
     */
    private void uploadFile(OSS ossClient, File file, String bucketName, String folder, String fileName) throws IOException {

        // 以输入流的形式上传文件
        InputStream is = new FileInputStream(file);
//            // 文件名
//            String fileName = file.getName();
//            fileName = UUID.randomUUID().toString().replaceAll("-","") + fileName;
        // 文件大小
        Long fileSize = file.length();
        // 创建上传Object的Metadata
        ObjectMetadata metadata = new ObjectMetadata();
        // 上传的文件的长度
        metadata.setContentLength(is.available());
        // 指定该Object被下载时的网页的缓存行为
        metadata.setCacheControl("no-cache");
        // 指定该Object下设置Header
        metadata.setHeader("Pragma", "no-cache");
        // 指定该Object被下载时的内容编码格式
        metadata.setContentEncoding("utf-8");
        // 文件的MIME，定义文件的类型及网页编码，决定浏览器将以什么形式、什么编码读取文件。如果用户没有指定则根据Key或文件名的扩展名生成，
        // 如果没有扩展名则填默认值application/octet-stream
        metadata.setContentType(getContentType(fileName));
        // 指定该Object被下载时的名称（指示MINME用户代理如何显示附加的文件，打开或下载，及文件名称）
        metadata.setContentDisposition("filename/filesize=" + fileName + "/" + fileSize + "Byte.");
        // 上传文件 (上传文件流的形式)
        PutObjectResult putResult = ossClient.putObject(bucketName, folder + "/" + fileName, is, metadata);

    }

    /**
     * 上传大文件
     * @param client
     * @param bucketName
     * @param key
     * @param uploadFile
     * @throws OSSException
     * @throws ClientException
     * @throws InterruptedException
     */
    private void uploadBigFile(OSS client, String bucketName, String key, File uploadFile)
            throws OSSException, ClientException, InterruptedException {

        int partCount = calPartCount(uploadFile);
        if (partCount <= 1) {
            throw new IllegalArgumentException("要上传文件的大小必须大于一个Part的字节数：" + PART_SIZE);
        }

        String uploadId = initMultipartUpload(client, bucketName, key);

        ExecutorService pool = Executors.newFixedThreadPool(CONCURRENCIES);

        List<PartETag> eTags = Collections.synchronizedList(new ArrayList<>());

        for (int i = 0; i < partCount; i++) {
            LOG.info("正在上传第" + i + "部分");
            long start = PART_SIZE * i;
            long curPartSize = PART_SIZE < uploadFile.length() - start ? PART_SIZE
                    : uploadFile.length() - start;

            pool.execute(new UploadPartThread(client, bucketName, key,
                    uploadFile, uploadId, i + 1, PART_SIZE * i, curPartSize,
                    eTags));
        }

        // 保存上传进度
        this.saveUploadProcess(partCount, 0,1, url);

        pool.shutdown();
        while (!pool.isTerminated()) {
            LOG.info(this.getUuid() + "已上传" + eTags.size());

            // 保存上传进度, 减1保证completeMultipartUpload被执行
            this.saveUploadProcess(partCount,(eTags.size()-1)<0?0:eTags.size(),1, url);

            pool.awaitTermination(5, TimeUnit.SECONDS);
        }

        if (eTags.size() != partCount) {

            // 保存上传进度
            this.saveUploadProcess(0,0,0, null);

            // 上传失败 加到FILE_SETTING
            throw new IllegalStateException("OSS Multipart上传失败，有Part未上传成功。");
        }

        completeMultipartUpload(client, bucketName, key, uploadId, eTags);

        // 保存上传进度
        this.saveUploadProcess(partCount, partCount,1, url);
    }

    // 根据文件的大小和每个Part的大小计算需要划分的Part个数。
    private int calPartCount(File f) {
        int partCount = (int) (f.length() / PART_SIZE);
        if (f.length() % PART_SIZE != 0) {
            partCount++;
        }
        return partCount;
    }

    private void saveUploadProcess(int total, int count, int code, String url) {

        // 保存上传结果
        JSONObject file_json = new JSONObject();
        file_json.put("total", total);
        file_json.put("count", count);
        file_json.put("url", url);
        file_json.put("code", code);
        FILE_SETTING.set(this.getUuid(), file_json.toJSONString());
        FILE_SETTING.store("file_upload.setting");//保存一次
    }

    // 初始化一个Multi-part upload请求。
    private String initMultipartUpload(OSS client, String bucketName, String key) throws OSSException, ClientException {
        InitiateMultipartUploadRequest initUploadRequest = new InitiateMultipartUploadRequest(bucketName, key);
        InitiateMultipartUploadResult initResult = client.initiateMultipartUpload(initUploadRequest);
        String uploadId = initResult.getUploadId();
        return uploadId;
    }

    // 完成一个multi-part请求。
    private void completeMultipartUpload(OSS client, String bucketName, String key, String uploadId, List<PartETag> eTags)
            throws OSSException, ClientException {

        // 为part按partnumber排序
        Collections.sort(eTags, new Comparator<PartETag>() {

            public int compare(PartETag arg0, PartETag arg1) {
                PartETag part1 = arg0;
                PartETag part2 = arg1;

                return part1.getPartNumber() - part2.getPartNumber();
            }
        });

        CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(
                bucketName, key, uploadId, eTags);

        client.completeMultipartUpload(completeMultipartUploadRequest);
    }

    private class UploadPartThread implements Runnable {
        private File uploadFile;
        private String bucket;
        private String object;
        private long start;
        private long size;
        private List<PartETag> eTags;
        private int partId;
        private OSS client;
        private String uploadId;

        UploadPartThread(OSS client, String bucket, String object,
                         File uploadFile, String uploadId, int partId, long start,
                         long partSize, List<PartETag> eTags) {
            this.uploadFile = uploadFile;
            this.bucket = bucket;
            this.object = object;
            this.start = start;
            this.size = partSize;
            this.eTags = eTags;
            this.partId = partId;
            this.client = client;
            this.uploadId = uploadId;
        }

        @Override
        public void run() {

            InputStream in = null;
            try {
                in = new FileInputStream(uploadFile);
                in.skip(start);

                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.setBucketName(bucket);
                uploadPartRequest.setKey(object);
                uploadPartRequest.setUploadId(uploadId);
                uploadPartRequest.setInputStream(in);
                uploadPartRequest.setPartSize(size);
                uploadPartRequest.setPartNumber(partId);

                UploadPartResult uploadPartResult = client.uploadPart(uploadPartRequest);

                eTags.add(uploadPartResult.getPartETag());

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (in != null)
                    try {
                        in.close();
                    } catch (Exception e) {
                    }
            }
        }
    }
}
